#include "ThreadPool.h"
#include <iostream>

ThreadPool::ThreadPool()
{
	Initialize(30,10);
}

ThreadPool::ThreadPool(int maxThreads, int minThreads)
{
	Initialize(maxThreads,minThreads);
}

ThreadPool::~ThreadPool()
{
	list<ThreadData *>::iterator itBegin;
	list<ThreadData *>::iterator itEnd;
	itBegin = idleThreads.begin();
	itEnd = idleThreads.end();
	while(itBegin != itEnd)
	{
		CloseHandle((*itBegin)->hEvent);
		TerminateThread((*itBegin)->threadHandle,0);
		CloseHandle((*itBegin)->threadHandle);
		delete (*itBegin);
		itBegin++;
	}
	itBegin = runningThreads.begin();
	itEnd = runningThreads.end();
	while(itBegin != itEnd)
	{
		CloseHandle((*itBegin)->hEvent);
		TerminateThread((*itBegin)->threadHandle,0);
		CloseHandle((*itBegin)->threadHandle);
		delete (*itBegin);
		itBegin++;
	}
}

void ThreadPool::Initialize(int maxThreads, int minThreads)
{
	this->maxThreads = maxThreads;
	this->minThreads = minThreads;
	int i = 0;
	for(i=0; i<minThreads; i++)
	{
		ThreadData * tmp = new ThreadData();
		tmp->hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
		tmp->threadHandle = CreateThread(NULL,0,ThreadPool::ThreadPoolFun,tmp,NULL,NULL);
		tmp->threadPosition = InIdle;
		tmp->threadWaitTime = 0;
		tmp->threadPool = this;
		idleThreads.push_back(tmp);
	}
	InitializeCriticalSection(&idleThreadsCriticalSection);
	InitializeCriticalSection(&runningThreadsCriticalSection);
	InitializeCriticalSection(&toExecutedItemsCriticalSection);
	threadSpanTime = 15000;
	threadWaitTime = 50;
}

ThreadData * ThreadPool::GetOneThread()
{
	ThreadData * resultThread = NULL;
	EnterCriticalSection(&idleThreadsCriticalSection);
	if (!idleThreads.empty())
	{
		resultThread = idleThreads.front();
		idleThreads.pop_front();
		EnterCriticalSection(&runningThreadsCriticalSection);
		runningThreads.push_back(resultThread);
		resultThread->threadPosition = InRunning;
		LeaveCriticalSection(&runningThreadsCriticalSection);
	}
	LeaveCriticalSection(&idleThreadsCriticalSection);
	return resultThread;
}

ThreadData * ThreadPool::CreateOneThread()
{
	ThreadData * resultThread = NULL;
	EnterCriticalSection(&idleThreadsCriticalSection);
	EnterCriticalSection(&runningThreadsCriticalSection);
	if(idleThreads.size() + runningThreads.size() < maxThreads)
	{
		resultThread = new ThreadData();
		resultThread->hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
		resultThread->threadHandle = CreateThread(NULL,0,ThreadPool::ThreadPoolFun,resultThread,NULL,NULL);
		resultThread->threadPosition = InRunning;
		resultThread->threadWaitTime = 0;
		resultThread->threadPool = this;
		runningThreads.push_back(resultThread);
	}
	LeaveCriticalSection(&runningThreadsCriticalSection);
	LeaveCriticalSection(&idleThreadsCriticalSection);
	return resultThread;
}

BOOL ThreadPool::IfCloseThread(ThreadData * threadData)
{
	BOOL result = TRUE;
	EnterCriticalSection(&idleThreadsCriticalSection);
	EnterCriticalSection(&runningThreadsCriticalSection);
	if(threadData->threadPosition != InIdle || idleThreads.size() + runningThreads.size() <= minThreads)
	{
		result = FALSE;
	}
	else
	{
		CloseHandle(threadData->hEvent);
		CloseHandle(threadData->threadHandle);
		delete threadData;
		idleThreads.remove(threadData);
		result = TRUE;
	}
	LeaveCriticalSection(&runningThreadsCriticalSection);
	LeaveCriticalSection(&idleThreadsCriticalSection);
	return result;
}

DWORD WINAPI ThreadPool::ThreadPoolFun(void * param)
{
	ThreadData * threadData = (ThreadData*)param;
	ThreadPool * threadPool = threadData->threadPool;
	while(true)
	{
		DWORD resultStatus = WaitForSingleObject(threadData->hEvent,threadPool->threadWaitTime);
		if(resultStatus == WAIT_TIMEOUT)
		{
			threadData->threadWaitTime += threadPool->threadWaitTime;
			if(threadData->threadWaitTime >= threadPool->threadSpanTime)
			{
				if(threadPool->IfCloseThread(threadData))
				{
					return 0;
				}
				else
				{
					threadData->threadWaitTime = 0;
					continue;
				}
			}
			else
			{
				while(true)
				{
					EnterCriticalSection(&threadPool->toExecutedItemsCriticalSection);
					if(threadPool->toExecutedItems.empty())
					{
						LeaveCriticalSection(&threadPool->toExecutedItemsCriticalSection);
						break;
					}
					else
					{
						Runnable * runnable = threadPool->toExecutedItems.front();
						threadPool->toExecutedItems.pop_front();
						runnable->Run();
					}
					LeaveCriticalSection(&threadPool->toExecutedItemsCriticalSection);
				}
				continue;
			}
		}	
		threadData->runnable->Run();
		EnterCriticalSection(&threadPool->idleThreadsCriticalSection);
		EnterCriticalSection(&threadPool->runningThreadsCriticalSection);
		threadPool->runningThreads.remove(threadData);
		threadPool->idleThreads.push_front(threadData);
		threadData->threadPosition = InIdle;
		LeaveCriticalSection(&threadPool->runningThreadsCriticalSection);
		LeaveCriticalSection(&threadPool->idleThreadsCriticalSection);
	}
	return 0;
}

void ThreadPool::Start(Runnable * runnable)
{
	ThreadData * threadData = GetOneThread();
	if(threadData == NULL)
	{
		threadData = CreateOneThread();
		if(threadData == NULL)
		{
			EnterCriticalSection(&toExecutedItemsCriticalSection);
			toExecutedItems.push_back(runnable);
			LeaveCriticalSection(&toExecutedItemsCriticalSection);
		}
		else
		{
			threadData->runnable = runnable;
			SetEvent(threadData->hEvent);
		}
	}
	else
	{
		threadData->runnable = runnable;
		SetEvent(threadData->hEvent);
	}
}